﻿using System.Globalization;
using Irony.Parsing;

namespace DPLParser
{
    [Language("DPL Grammar", "1.0", "Event description grammar")]
    public class DPLGrammar : Grammar
    {
        public DPLGrammar()
        {
            #region ADD PONCTUATION
            this.Delimiters = "{}[](),:;+-*/%&|^!~<>=";
            this.MarkPunctuation(";", ",", "(", ")", "{", "}", "[", "]", ":", "*");
            KeyTerm Lpar = ToTerm("(");
            KeyTerm Rpar = ToTerm(")");
            KeyTerm equals = ToTerm("=");
            KeyTerm lbr = ToTerm("{");
            KeyTerm rbr = ToTerm("}");
            RegisterOperators(-3, "=", "+", "-", "*", "<", ">", "~", "*");

            CommentTerminal SingleLineComment = new CommentTerminal("SingleLineComment", "//", "\r", "\n", "\u2085", "\u2028", "\u2029");
            CommentTerminal DelimitedComment = new CommentTerminal("DelimitedComment", "/*", "*/");
            NonGrammarTerminals.Add(SingleLineComment);
            NonGrammarTerminals.Add(DelimitedComment);
            #endregion

            #region PROGRAM DECLARATION

            IdentifierTerminal PlainVariable = ApplicationIdentifier("VariableDefinition");
            #endregion

            var PlainVariableFactorial = new NonTerminal("PlainVariableFactorial");
            PlainVariableFactorial.Rule = PlainVariable + "!";

            IdentifierTerminal ModuleName = ApplicationIdentifier("ModuleName");
            IdentifierTerminal PatternName = ApplicationIdentifier("PatternName");

            var DirectOperatorName = new NonTerminal("DirectOperatorName");
            DirectOperatorName.Rule = ToTerm("CONTR") | ToTerm("CALLER") | ToTerm("TYPE");


            // RegexBasedTerminal OperandDigit = new RegexBasedTerminal(@"2");
            var OperandDigit = new NonTerminal("OperandDigit");
            OperandDigit.Rule = ToTerm("1") | ToTerm("2") | ToTerm("3") | ToTerm("4") | ToTerm("5") | ToTerm("6") | ToTerm("7") | ToTerm("8") | ToTerm("9");

            var VariableDefinitionWithExposure = new NonTerminal("VariableDefinitionWithExposure");
            VariableDefinitionWithExposure.Rule = PlainVariable + "^" + OperandDigit
                                                 | PlainVariable + "^" + "(1/" + OperandDigit + ")"
                                                 | PlainVariable + "^0";

            var OperationMember = new NonTerminal("OperationMember");

            var VariableDefinition = new NonTerminal("VariableDefinition");
            VariableDefinition.Rule = PlainVariable
                                      | "[" + PlainVariable + "]"
                                      | VariableDefinitionWithExposure
                                      | OperationMember;


            var ArgVariablesList = new NonTerminal("VariablesList");
            var comma = ToTerm(",");
            ArgVariablesList.Rule = MakeStarRule(ArgVariablesList, comma, VariableDefinition);

            var VariableSet = new NonTerminal("VariableSet");
            VariableSet.Rule = lbr + ArgVariablesList + rbr;

            var VariableSetOrVariable = new NonTerminal("VariableSetOrVariable");
            VariableSetOrVariable.Rule = VariableSet | VariableDefinition;

            var StarOperator = new NonTerminal("StarOperator");
            StarOperator.Rule = ToTerm("*");

            var VariablesMultiplication = new NonTerminal("VariablesMultiplication");
            VariablesMultiplication.Rule = VariableDefinition
                                           | (VariableDefinition + "*" + VariablesMultiplication);

            var VariablesMultiplicationSet = new NonTerminal("VariablesMultiplicationSet");
            VariablesMultiplicationSet.Rule = "{" + VariablesMultiplication + "}";



            var VariableSetOrVariableOrOperationMember = new NonTerminal("VariableSetOrVariableOrOperationMember");
            VariableSetOrVariableOrOperationMember.Rule = VariableSetOrVariable | OperationMember;

            var EmptySet = new NonTerminal("EmptyResultSet");
            EmptySet.Rule = lbr + rbr;

            // express the composite nature of operations
            /*
            OPER	(A)
            OPER+3	(A)
            OPER-3	(A)
            OPER+3*	(A)
            OPER-3*	(A)
            OPER*	(A)
            OPER-*	(A)
            */
            OperationMember.Rule = DirectOperatorName + ToTerm("(") + VariableSetOrVariableOrOperationMember +
                                   ToTerm(")")
                                   |
                                   DirectOperatorName + "+" + OperandDigit + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")")
                                   |
                                   DirectOperatorName + "-" + OperandDigit + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")")
                                   |
                                   DirectOperatorName + "+" + OperandDigit + StarOperator + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")")
                                   |
                                   DirectOperatorName + "+" + OperandDigit + StarOperator + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")")
                                   |
                                   DirectOperatorName + "-" + OperandDigit + StarOperator + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")")
                                   |
                                   DirectOperatorName + StarOperator + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")")
                                   |
                                   DirectOperatorName + "-" + StarOperator + ToTerm("(") +
                                   VariableSetOrVariableOrOperationMember + ToTerm(")");

            var RightOperationMember = new NonTerminal("RightOperationMember");
            RightOperationMember.Rule = OperationMember
                                        | VariableSetOrVariableOrOperationMember
                                        | EmptySet
                                        | VariablesMultiplicationSet;

            var LeftOperationMember = new NonTerminal("LeftOperationMember");
            LeftOperationMember.Rule = OperationMember;

            var DPLOperator = new NonTerminal("DPLOperator");
            DPLOperator.Rule = ToTerm("=") | ToTerm("<") | ToTerm("<(1..1)") | ToTerm("<(1..N)")
                               | ToTerm("<(N..1)") | ToTerm("<(N..N)") | ToTerm("~(N..N)~") | ToTerm("~(1..N)~")
                               | ToTerm("~(N..1)~") | ToTerm("~(N..N)~");

            var Operation = new NonTerminal("Operation");
            Operation.Rule = LeftOperationMember + DPLOperator + RightOperationMember
                            | PlainVariableFactorial;

            var OperationDeclarations = new NonTerminal("OperationDeclarations");
            OperationDeclarations.Rule = MakePlusRule(OperationDeclarations, Operation);

            var PlainVariablesList = new NonTerminal("PlainVariablesList");
            PlainVariablesList.Rule = MakeStarRule(PlainVariablesList, comma, PlainVariable);

            var PatternDeclarations = new NonTerminal("PatternDeclarations");
            var PatternDeclaration = new NonTerminal("PatternDeclaration");

            PatternDeclaration.Rule = ToTerm("Pattern") + PatternName + lbr + OperationDeclarations.Q() + rbr
                                      | ToTerm("Pattern") + PatternName + "(" + PlainVariablesList + ")" + lbr + OperationDeclarations.Q() + rbr;


            PatternDeclarations.Rule = MakeStarRule(PatternDeclarations, PatternDeclaration);
            var compilation_unit = new NonTerminal("compilation_unit");

            var PatternDeclarationBlock = new NonTerminal("PatternDeclarationBlock");
            PatternDeclarationBlock.Rule = lbr + PatternDeclarations.Q() + rbr;
            compilation_unit.Rule = "module" + ModuleName + PatternDeclarationBlock;

            this.Root = compilation_unit;
            MarkPunctuation("{", "}", ";");

        }

        public static IdentifierTerminal ApplicationIdentifier(string name)
        {
            IdentifierTerminal id = new IdentifierTerminal(name, IdOptions.AllowsEscapes | IdOptions.CanStartWithEscape);
            id.AddPrefix("@", IdOptions.IsNotKeyword);
            //From spec:
            //Start char is "_" or letter-character, which is a Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl 
            id.StartCharCategories.AddRange(new UnicodeCategory[] {
             UnicodeCategory.UppercaseLetter, //Ul
             UnicodeCategory.LowercaseLetter, //Ll
             UnicodeCategory.TitlecaseLetter, //Lt
             UnicodeCategory.ModifierLetter,  //Lm
             UnicodeCategory.OtherLetter,     //Lo
             UnicodeCategory.LetterNumber     //Nl
            });
            //Internal chars
            /* From spec:
            identifier-part-character: letter-character | decimal-digit-character | connecting-character |  combining-character |
                formatting-character
             */
            id.CharCategories.AddRange(id.StartCharCategories); //letter-character categories
            id.CharCategories.AddRange(new UnicodeCategory[] {
            UnicodeCategory.DecimalDigitNumber, //Nd
            UnicodeCategory.ConnectorPunctuation, //Pc
            UnicodeCategory.SpacingCombiningMark, //Mc
            UnicodeCategory.NonSpacingMark,       //Mn
            UnicodeCategory.Format                //Cf
            });
            //Chars to remove from final identifier
            id.CharsToRemoveCategories.Add(UnicodeCategory.Format);
            return id;
        }

        public void RegisterOperators()
        {
            RegisterOperators(1, "||");
            RegisterOperators(2, "&&");
            RegisterOperators(3, "|");
            RegisterOperators(4, "^");
            RegisterOperators(5, "&");
            RegisterOperators(6, "==", "!=");
            RegisterOperators(7, "<", ">", "<=", ">=");
            RegisterOperators(8, "+", "-");
            RegisterOperators(9, "*", "/", "%");
            RegisterOperators(10, ".");
        }

    }
}
